using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using NLog.Extensions.Logging;
using Swashbuckle.AspNetCore.Filters;
using System.Reflection;
using System.Text.RegularExpressions;
using WAU.Common;
using WAU.Common.Converters;
using WAU.Common.Middlewares;
using WAU.DAL;
using WAU.Library.RabbitMQ;
using WAU.Models.Framework;
using WAU.Services.Routines;

namespace WAU
{
    /// <summary>
    /// Program
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Main
        /// </summary>
        /// <param name="args"></param>
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(new WebApplicationOptions
            {
                Args = args,
                WebRootPath = "wwwroot"
            });

            builder.Configuration.Bind(GlobalConfig.Setting);
            
            builder.Logging.ClearProviders();
            builder.Logging.AddConsole();            
            builder.Logging.AddNLog("NlogConfig.xml");

            #region 添加服务

            builder.Services.AddControllers().AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.Converters.Add(DatetimeJsonConverter.Ins);
                options.JsonSerializerOptions.Converters.Add(DateOnlyJsonConverter.Ins);
            });

            builder.Services.AddStackExchangeRedisCache(options =>
            {
                options.InstanceName = $"{nameof(WAU)}:";
                options.Configuration = GlobalConfig.Setting.ConnectionStrings.Redis;
            });

            builder.Services.AddRabbitMQConnectionPool(
                new PooledRabbitMQModelPolicy(GlobalConfig.Setting.AMQP), 10);

            //// 配置跨域策略
            //builder.Services.AddCors(options =>
            //{
            //    options.AddPolicy("my-cors", policy =>
            //    {
            //        policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
            //    });
            //})

            builder.Services.Configure<FormOptions>(option => option.MultipartBodyLengthLimit = long.MaxValue) ;

            //builder.Services.AddDistributedMemoryCache();

            // 仅用于登录/注册时的验证码，不通过 Session 控制登录状态
            builder.Services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.Parse("00:15:00");
                options.Cookie.HttpOnly = true;
            });

            // 数据库连接池
            builder.Services.AddDbContextPool<EssentialDbContext>(optionsBuilder =>
            {
                optionsBuilder = optionsBuilder.UseMySql(GlobalConfig.Setting.ConnectionStrings.MySQL,
                                                        ServerVersion.Parse("8.0.28-mysql"));

                // 开发环境时记录敏感信息
                if (builder.Environment.IsDevelopment())
                {
                    optionsBuilder
                        .LogTo(Console.WriteLine, LogLevel.Information)
                        .EnableSensitiveDataLogging()
                        .EnableDetailedErrors();

                }
            });

            builder.Services.AddScoped<JwtHelper>();

            builder.Services.AddScoped(typeof(BaseRepoMysql<>));

            #region 注入 Services 层的内容

            /*
             *  读取 Services 层所有 以 Service 结尾的方法（如果还有 ` 则表示为泛型类型）
             */

            Assembly assembly = Assembly.Load("WAU.Services");
            Regex bServiceNameReg = new Regex("Service(`\\d+)?$");  // 可能会有泛型类（但无需显式指出泛型参数）
            Type[] bServicesTypes = assembly.GetTypes()
                                        .Where(type => type.IsPublic && bServiceNameReg.IsMatch(type.FullName))
                                        .ToArray();
            foreach (Type bServicesType in bServicesTypes)
            {
                builder.Services.AddScoped(bServicesType);
            }

            #endregion

            builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = JwtHelper.ValidationParameters;

                    options.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            if(context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                // 在前台过期刷新
                                context.Response.Headers.Add("Token-Expired", "true");
                            }

                            return Task.CompletedTask;
                        }
                    };
                });


            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen(option =>
            {
                option.SwaggerDoc("v1", new OpenApiInfo { Title = "EssentialFramework", Version = "v1" });

                // 使用反射获取xml文件。并构造出文件的路径
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                // 启用xml注释. 该方法第二个参数启用控制器的注释，默认为false.
                option.IncludeXmlComments(xmlPath, true);

                // 在header中添加token，传递到后台
                option.OperationFilter<SecurityRequirementsOperationFilter>();

                #region Token绑定到ConfigureServices

                option.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入 Bearer {token}（注意两者之间是一个空格）",
                    Name = "Authorization",//jwt默认的参数名称
                    In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
                    Type = SecuritySchemeType.ApiKey
                });

                #endregion
            });

            // 请求大小限制
            builder.Services.Configure<KestrelServerOptions>(options =>
            {
                options.Limits.MaxRequestBodySize = null;
            });

            #endregion

            var app = builder.Build();

            #region 使用中间件

            // 开发环境时启用 错误页 和 swagger
            // 生产环境时启用 重定向
            if (builder.Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();

                app.UseSwagger();
                app.UseSwaggerUI(options =>
                {
                    options.SwaggerEndpoint("/swagger/v1/swagger.json", "WAU");
                });
            }
            else
            {
                app.UseHttpsRedirection();  // 将 http 重定向到 https（需要同时监听 http 端口 和 https 端口）
            }

            app.UseDefaultFiles(new DefaultFilesOptions());

            // 静态资源中间件
            app.UseStaticFiles();

            // 异常处理中间件
            app.UseExceptionHandler(configure =>
            {
                configure.Run(async context =>
                {
                    var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
                    var exption = exceptionHandlerFeature?.Error;
                    await CustomExceptionHandler.OnExceptionAsync(context, exption);
                });
            });

            app.UseMiddleware<RequestBodyStreamOptimizeMiddleware>();

            app.UseMiddleware<AccessLogMiddleware>();

            app.UseRouting();

            //使用跨域策略
            //app.UseCors(optoins =>
            //{
            //    optoins.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
            //});

            app.UseMiddleware<InvalidTokenFilterMiddleware>();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseMiddleware<RequestThrottleMiddleaware>();

            app.UseSession();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            GlobalReadOnlyVariables.ServiceProvider = app.Services;

            #endregion

            new MQConsumeOrderRoutine().Execute();
            new MQConsumeOrderExpireRoutine().Execute();

            app.Run();
        }
    }
}